/* command.c, Ait, BSD 3-Clause, Kevin Bloom, 2023-2025,
   Derived from: Atto January 2017
   Derived from: AnthonyEditor January 93
*/

#include <sys/types.h>
#include "header.h"
#include "termbox.h"
#include "util.h"

void quit() { done = 1; }
void up()
{
  curbp->b_point = lncolumn(curbp, upup(curbp, curwp, curbp->b_point),curbp->b_pcol - curwp->w_left);
}
void down()
{
  curbp->b_point = lncolumn(curbp, dndn(curbp, curwp, curbp->b_point),curbp->b_pcol - curwp->w_left);
}
void lnbegin()
{
  char_t *p;
  if(curbp->b_point == 0)
    return;
  p = ptr(curbp, curbp->b_point);
  while(*(p = ptr(curbp, curbp->b_point-1)) != '\n' && p > curbp->b_buf)
    --curbp->b_point;
  if(curbp->b_point != 0 && p == curbp->b_buf && curbp->b_line == 1)
    --curbp->b_point;
  curbp->b_col = 0 + curwp->w_left;
}
void version() { msg(VERSION); }
void top()
{
  shift_pmark(TRUE, curbp->b_point);
  curbp->b_point = 0;
  curbp->b_pcol = 0 + curwp->w_left;
  shift_pmark(TRUE, curbp->b_point);
}
void bottom()
{
  shift_pmark(TRUE, curbp->b_point);
  curbp->b_point = pos(curbp, curbp->b_ebuf);
  if (curbp->b_epage < pos(curbp, curbp->b_ebuf))
    curbp->b_reframe = 1;
  curbp->b_pcol = 0 + curwp->w_left;
  shift_pmark(TRUE, curbp->b_point);
}
void block() { curbp->b_mark = curbp->b_point; }
void copy() { copy_cut(FALSE, TRUE, FALSE); }
void cut() {
  currentcommand = KBD_CUT;
  copy_cut(TRUE, TRUE, FALSE);
}
void resize_terminal()
{
  LINES = tb_height();
  COLS = tb_width();
  MSGLINE = LINES-1;
  resize();
}

void print_to_msgline(const char *msg)
{
  printf_tb(0, MSGLINE, TB_DEFAULT, TB_DEFAULT, msg);
  tb_set_cursor(strlen(msg), MSGLINE);
}

void quit_ask()
{
  if (modified_buffers() > 0) {
    const char *msg = "Modified buffers exist; really exit (y/N) ?";
    int size = strlen(msg);
    print_to_msgline(msg);
    clrtoeol(size, MSGLINE);
    if (!yesno(FALSE)) {
      clrtoeol(0, MSGLINE);
      return;
    }
  }
  quit();
}


void redraw()
{
  window_t *wp;
  for (wp=wheadp; wp != NULL; wp = wp->w_next)
    wp->w_update = TRUE;
  update_display();
}

void left()
{
  int n = prev_utf8_char_size();
  if(curbp->b_point == 0)
    return;
  while (0 < curbp->b_point && n-- > 0)
    --curbp->b_point;
}

void right()
{
  if(curbp->b_point == pos(curbp, curbp->b_ebuf))
    return;
  int n = utf8_size(*ptr(curbp,curbp->b_point));
  while ((curbp->b_point < pos(curbp, curbp->b_ebuf)) && n-- > 0)
    ++curbp->b_point;
}

/* work out number of bytes based on first byte */
int utf8_size(char_t c)
{
  return tb_utf8_char_length(c);
}

int prev_utf8_char_size()
{
  int n;
  for (n=2;n<5;n++)
    if (-1 < curbp->b_point - n && (utf8_size(*(ptr(curbp, curbp->b_point - n))) == n))
      return n;
  return 1;
}

void lnend()
{
  char_t *p;
  int cols = 0;
  lnbegin();      // reset the line so we get the right number for `cols`
  while(*(p = ptr(curbp, curbp->b_point)) != '\n' && curbp->b_ebuf > p) {
    ++curbp->b_point;
  }
  /* loop until we get to the correct column */
  while(cols > curwp->w_cols) {
    cols -= curwp->w_cols;
  }
  curbp->b_pcol = cols + curwp->w_left;      // set it for column-memory
}

void wleft()
{
  char_t *p = NULL;
  if ((curbp->b_buf < p || p == NULL) && (!isspace(*(p = ptr(curbp, curbp->b_point))) || !is_symbol(*p)))
    --curbp->b_point;
  while (curbp->b_buf < p && (isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)) && curbp->b_buf < p)
    --curbp->b_point;
  while (curbp->b_buf < p && !isspace(*(p = ptr(curbp, curbp->b_point))) && !is_symbol(*p) && curbp->b_buf < p)
    --curbp->b_point;
  if(curbp->b_point < 0 || (curbp->b_buf < p && (isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p))))
    ++curbp->b_point;
}

void wleftdelete()
{
  currentcommand = KBD_DELETE_WORD;
  iblock();
  wleft();
  copy_cut(TRUE, TRUE, FALSE);
}

void pgdown()
{
  shift_pmark(TRUE, curbp->b_point);
  curbp->b_page = curbp->b_point = upup(curbp, curwp, curbp->b_epage);
  while (0 < curwp->w_top - curbp->b_row) {
    down();
    curbp->b_row--;
  }
  curbp->b_epage = pos(curbp, curbp->b_ebuf);
  curbp->b_pcol = 0 + curwp->w_left;
  shift_pmark(TRUE, curbp->b_point);
}

void pgup()
{
  int i = curwp->w_rows;
  shift_pmark(TRUE, curbp->b_point);
  while (0 < --i) {
    curbp->b_page = upup(curbp, curwp, curbp->b_page);
    up();
  }
  curbp->b_pcol = 0 + curwp->w_left;
  shift_pmark(TRUE, curbp->b_point);
}

void wright()
{
  char_t *p = ptr(curbp, curbp->b_point);
//   if (p < curbp->b_ebuf && (!isspace(*(p = ptr(curbp, curbp->b_point))) || !is_symbol(*p)))
//     ++curbp->b_point;
  while (p < curbp->b_ebuf && (isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)))
    ++curbp->b_point;
  while (p < curbp->b_ebuf && !isspace(*(p = ptr(curbp, curbp->b_point))) && !is_symbol(*p))
    ++curbp->b_point;
}

void wrightdelete()
{
  currentcommand = KBD_DELETE_WORD;
  iblock();
  wright();
  copy_cut(TRUE, TRUE, FALSE);
}

void insert()
{
  assert(curbp->b_gap <= curbp->b_egap);
  if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK))
    return;
  curbp->b_point = movegap(curbp, curbp->b_point);
  /* overwrite if mid line, not EOL or EOF, CR will insert as normal */
  if ((curbp->b_flags & B_OVERWRITE) && *input != '\r' && *(ptr(curbp, curbp->b_point)) != '\n' && curbp->b_point < pos(curbp,curbp->b_ebuf) ) {
    *(ptr(curbp, curbp->b_point)) = *input;
    if (curbp->b_point < pos(curbp, curbp->b_ebuf))
      ++curbp->b_point;
  } else {
    *curbp->b_gap++ = *input == '\r' ? '\n' : *input;
    curbp->b_point = pos(curbp, curbp->b_egap);
    // force reframe if scrolled off bottom of screen and at EOF
    if (curbp->b_point == pos(curbp, curbp->b_ebuf) && curbp->b_point >= curbp->b_epage &&
      curwp->w_rows == (curwp->w_row - curwp->w_top))
     curbp->b_reframe = 1;
  }
  curbp->b_flags |= B_MODIFIED;
  undoset_flag = TRUE;
  currentcommand = KBD_INSERT;
}

void insert_str()
{
  int len = strlen((const char *)input);
  assert(curbp->b_gap <= curbp->b_egap);
  undoset(INSERT, FALSE);
  if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK))
    return;
  curbp->b_point = movegap(curbp, curbp->b_point);
  /* overwrite if mid line, not EOL or EOF, CR will insert as normal */
  if ((curbp->b_flags & B_OVERWRITE) && input[0] != '\r' && *(ptr(curbp, curbp->b_point)) != '\n' && curbp->b_point < pos(curbp,curbp->b_ebuf) ) {
    *(ptr(curbp, curbp->b_point)) = *input;
    if (curbp->b_point < pos(curbp, curbp->b_ebuf))
      ++curbp->b_point;
  } else {
    for(int i = 0; i < len; i++) {
      *curbp->b_gap++ = input[i] == '\r' ? '\n' : input[i];
//      if(input[i] == '\n' || input[i] == '\r')
//        curbp->b_line++;
    }
    curbp->b_point = pos(curbp, curbp->b_egap);
    // force reframe if scrolled off bottom of screen and at EOF
    if (curbp->b_point == pos(curbp, curbp->b_ebuf) && curbp->b_point >= curbp->b_epage &&
        curwp->w_rows == (curwp->w_row - curwp->w_top))
      curbp->b_reframe = 1;
  }
  curbp->b_flags |= B_MODIFIED;
  undoset_flag = TRUE;
}

void insert_unicode()
{
  int len = strlen((const char *)unicode_buf);
  assert(curbp->b_gap <= curbp->b_egap);
  undoset(INSERT, lastcommand == KBD_INSERT);
  if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK))
    return;
  curbp->b_point = movegap(curbp, curbp->b_point);
  /* overwrite if mid line, not EOL or EOF, CR will insert as normal */
  for(int i = 0; i < len; i++) {
    *curbp->b_gap++ = unicode_buf[i];
  }
  curbp->b_point = pos(curbp, curbp->b_egap);
  // force reframe if scrolled off bottom of screen and at EOF
  if (curbp->b_point == pos(curbp, curbp->b_ebuf) && curbp->b_point >= curbp->b_epage &&
      curwp->w_rows == (curwp->w_row - curwp->w_top))
    curbp->b_reframe = 1;
  curbp->b_flags |= B_MODIFIED;
  undoset_flag = TRUE;
  unicode_buf[0] = '\0';
  currentcommand = KBD_INSERT;
}

void backsp()
{
  int dl = FALSE;
  undoset(BACKSP, lastcommand == KBD_DELETE_CHAR);
  if(curbp->b_point != 0 && *ptr(curbp, curbp->b_point - 1) == '\n') {
      curbp->b_line--;
      dl = TRUE;
  }
  curbp->b_point = movegap(curbp, curbp->b_point);
  if (curbp->b_buf < curbp->b_gap) {
    curbp->b_gap -= prev_utf8_char_size();
    curbp->b_flags |= B_MODIFIED;
  }
  curbp->b_point = pos(curbp, curbp->b_egap);
  // reset the presistent column, if we deleted the line
  if(dl) {
    point_t opoint = curbp->b_point;
    int cols = 0;
    lnbegin();
    while(curbp->b_point != opoint) {
      ++curbp->b_point;
      cols++;
    }
    curbp->b_pcol = cols + curwp->w_left;
  }
  currentcommand = KBD_DELETE_CHAR;
}

void delete()
{
  undoset(DELETE, lastcommand == KBD_DELETE_CHAR);
  curbp->b_point = movegap(curbp, curbp->b_point);
  if (curbp->b_egap < curbp->b_ebuf) {
    curbp->b_egap += utf8_size(*curbp->b_egap);
    curbp->b_point = pos(curbp, curbp->b_egap);
    curbp->b_flags |= B_MODIFIED;
  }
  currentcommand = KBD_DELETE_CHAR;
}

void gotoline()
{
  int line, showdefault = lastline > 0;
  point_t p;
  char *prompt;
  if(showdefault)
    asprintf(&prompt, "Goto line (default %d): ", lastline);
  else
    asprintf(&prompt, "Goto line: ");
  if (getinput(prompt, temp, STRBUF_S, F_CLEAR, showdefault)) {
    line = temp[0] == 0 && showdefault ? lastline : atoi(temp);
    p = line_to_point(line);
    if (p != -1) {
      shift_pmark(TRUE, curbp->b_point);
      curbp->b_point = p;
      curbp->b_pcol = 0 + curwp->w_left;
      if (curbp->b_epage < pos(curbp, curbp->b_ebuf)) curbp->b_reframe = 1;
      curwp->w_update = TRUE;
      curwp->w_recenter = TRUE;
      lastline = line;
      msg("Line %d", line);
      shift_pmark(TRUE, curbp->b_point);
    } else {
      msg("Line %d, not found", line);
    }
  }
  clrtoeol(0, MSGLINE);
  free(prompt);
  prompt = NULL;
}

void gotocolumn()
{
  int col, remainder = 0, showdefault = lastcol > 0;
  point_t opoint = curbp->b_point, end = pos(curbp, curbp->b_ebuf);
  char *prompt;
  shift_pmark(TRUE, curbp->b_point);
  if(showdefault)
    asprintf(&prompt, "Goto column (default %d): ", lastcol);
  else
    asprintf(&prompt, "Goto column: ");
  if (getinput(prompt, temp, STRBUF_S, F_CLEAR, showdefault)) {
    col = temp[0] == 0 && showdefault ? lastcol : atoi(temp);
    remainder =  col - (curwp->w_col - curwp->w_left) - 1;
    if(remainder > 0) {
      if(*ptr(curbp, curbp->b_point) == '\n') {
        msg("Column %d, not found.", col);
        return;
      }
      for(; remainder > 0; remainder--) {
        right();
        if((*ptr(curbp, curbp->b_point) == '\n' ||
            curbp->b_point == end) &&
           remainder != 1) {
          curbp->b_point = opoint;
          msg("Column %d, not found.", col);
          return;
        }
      }
    } else if(remainder < 0) {

      remainder *= -1;
      if(curbp->b_point == 0 ||
         *ptr(curbp, curbp->b_point - 1) == '\n') {
        msg("Column %d, not found.", col);
        return;
      }
      for(; remainder > 0; remainder--) {
        left();
        if((*ptr(curbp, curbp->b_point - 1) == '\n' ||
            curbp->b_point == 0) &&
           remainder != 1) {
          curbp->b_point = opoint;
          msg("Column %d, not found.", col);
          return;
        }
      }
    }
    lastcol = col;
  }
  shift_pmark(TRUE, curbp->b_point);
  clrtoeol(0, MSGLINE);
  free(prompt);
  prompt = NULL;
}

void jumptorow()
{
  int line = -1, j = 0, i = 0, current, lastln, pageln;
  char num[3] = { 0, 0, 0 };
  struct tb_event ev;
  char *prompt = "Jump to line reference: ";
  int start_col = strlen(prompt), match = FALSE;
  char opts[10] = {'f','j','d','k','s','l','g','h', 'a', ';'};
  char chars[curwp->w_rows][2];
  point_t point;
  char_t *p;
  char f, s;
  int count = 0, fp = 0, sp = 0;
  int w_row = curwp->w_row - curwp->w_top;

  get_line_stats(&current, &lastln, curbp);

  pageln = current - w_row;
  point = curbp->b_page;
  p = ptr(curbp, point);

  for(int i = 0; i < curwp->w_rows && pageln <= lastln; i++) {
    f = opts[fp];
    s = opts[sp];
    chars[i][0] = f;
    chars[i][1] = s;
    printf_tb(curwp->w_left, curwp->w_top+i, TB_RED, TB_CYAN, "%c%c", f,s);
    sp++;
    count++;
    if(count > 7) {
      fp++;
      sp = 0;
      count = 0;
    }
    int c = 1;
    while(*(p = ptr(curbp, point)) != '\n' &&
          curbp->b_ebuf > p && c < curwp->w_cols) {
      ++point;
      c++;
    }
    if(*p == '\n' || pageln == lastln)
      pageln++;
    ++point;
    p = ptr(curbp, point);
  }

  display_prompt_and_response(prompt, num);
  tb_present();
  while(j < 2) {
    display_prompt_and_response(prompt, num);
    tb_present();
    if(execute_kbd_macro) {
      use_kbd_macro(&ev);
    } else if(tb_poll_event(&ev) != TB_OK)
      break;

    if(record_input) {
      record_buffer[record_buffer_index] = ev;
      record_buffer_index++;
    }

    if(ev.key == TB_KEY_CTRL_G) {
      clrtoeol(0, MSGLINE);
      return;
    }
    if(j < 2) {
      num[j] = ev.ch;
      tb_set_cursor(start_col, MSGLINE);
      addstr(num);
      point = curbp->b_page;
      p = ptr(curbp, point);
      int i = 0;
      if(j == 0) {
        pageln = current - w_row;
        for(i = 0; i < curwp->w_rows && pageln <= lastln; i++) {
          if(chars[i][0] == ev.ch) {
            match = TRUE;
            if(*p != '\n')
              p = ptr(curbp, point+1);
            if(*p == '\0')
              *p = ' ';
            printf_tb(curwp->w_left, curwp->w_top+i, TB_RED, TB_CYAN, "%c", chars[i][1]);
            printf_tb(curwp->w_left+1, curwp->w_top+i, TB_DEFAULT, TB_DEFAULT, "%c", *p == '\n' ? ' ' : *p);
          } else {
            printf_tb(curwp->w_left, curwp->w_top+i, TB_DEFAULT, TB_DEFAULT, "%c", *p == '\n' ? ' ' : *p);
            if(*p != '\n')
              p = ptr(curbp, point+1);
            if(*p == '\0')
              *p = ' ';
            printf_tb(curwp->w_left+1, curwp->w_top+i, TB_DEFAULT, TB_DEFAULT, "%c", *p == '\n' ? ' ' : *p);
          }
          int c = 1;
          while(*(p = ptr(curbp, point)) != '\n' &&
                curbp->b_ebuf > p && c < curwp->w_cols) {
            ++point;
            c++;
          }
          if(curbp->b_ebuf == p && chars[i][0] != ev.ch) {
            printf_tb(curwp->w_left, curwp->w_top+i, TB_DEFAULT, TB_DEFAULT, "%c", ' ');
          }
          if(*p == '\n' || pageln == lastln)
            pageln++;
          ++point;
          p = ptr(curbp, point);
        }
      }
      j++;
    }
    if(!match) {
      clrtoeol(0, MSGLINE);
      return;
    }
  }
  for(; i < curwp->w_rows; i++) {
    if(chars[i][0] == num[0] && chars[i][1] == num[1]) {
      line = w_row - i;
      break;
    }
  }
  if(i == curwp->w_rows) {
    msg("Out of bounds");
    return;
  }
  shift_pmark(TRUE, curbp->b_point);
  if(line > 0) {
    for(; line > 0; line--) {
        up();
    }
  } else {
    for(; line < 0; line++) {
        down();
    }
  }
  shift_pmark(TRUE, curbp->b_point);
  clrtoeol(0, MSGLINE);
}

void jumpword()
{
  point_t current = curbp->b_page;
  char num[3] = { 0, 0, 0 };
  int j = 0, match = FALSE;
  char starting[1] = "";
  struct tb_event ev;
  char *prompt = "Jump to word starting with: ";
  int start_col = strlen(prompt);
  char opts[10] = {'f','j','d','k','s','l','g','h', 'a', ';'};
  int diff = curbp->b_epage - curbp->b_page;
  char chars[diff][2];
  point_t point = -1;
  int begin = TRUE, is_white = FALSE, is_symb = FALSE, charlen = 0;
  char_t *p, *tp;

  char f, s;
  int count = 0, fp = 0, sp = 0, x = 0, y = 0;

  display_prompt_and_response(prompt, starting);
  tb_present();
  if(execute_kbd_macro) {
      use_kbd_macro(&ev);
  } else if(tb_poll_event(&ev) != TB_OK)
    return;

  if(record_input) {
    record_buffer[record_buffer_index] = ev;
    record_buffer_index++;
  }

  if(ev.key == TB_KEY_CTRL_G) {
    clrtoeol(0, MSGLINE);
    return;
  }

  starting[0] = (unsigned)ev.ch;

  for(; current < curbp->b_epage; current++) {
    p = ptr(curbp, current);
    is_white = isspace(*p);
    is_symb = is_symbol(*p);
    if(is_white || is_symb || current == 0)
      begin = TRUE;

    if(*p == (char_t)starting[0] && begin) {
      f = opts[fp];
      s = opts[sp];
      chars[current-curbp->b_page][0] = f;
      chars[current-curbp->b_page][1] = s;
      charlen++;
      printf_tb(curwp->w_left+x, curwp->w_top+y, TB_RED, TB_CYAN, "%c%c", f,s);
      sp++;
      count++;
      if(count > 7) {
        fp++;
        sp = 0;
        count = 0;
      }
      begin = FALSE;
    }

    if(!is_white && !is_symb)
      begin = FALSE;

    x++;
    if(*p == '\t')
      x += (TAB_SIZE - 2);
    if(*p < 31)
      x++;
    if(*p == '\n' || x >= curwp->w_cols) {
      x = 0;
      y++;
    }
  }
  tb_present();
  if(charlen > 1) {
    display_prompt_and_response(prompt, num);
    tb_present();
    while(j < 2) {
      display_prompt_and_response(prompt, num);
      tb_present();
      if(tb_poll_event(&ev) != TB_OK) break;
      if(ev.key == TB_KEY_CTRL_G) {
        clrtoeol(0, MSGLINE);
        return;
      }
      if(j < 2) {
        num[j] = ev.ch;
        tb_set_cursor(start_col, MSGLINE);
        addstr(num);
        x = 0;
        y = 0;
        if(j == 0) {
          for(current = curbp->b_page; current < curbp->b_epage; current++) {
            p = ptr(curbp, current);
            tp = ptr(curbp, current);
            is_white = isspace(*p);
            is_symb = is_symbol(*p);
            if(is_white || is_symb || current == 0)
              begin = TRUE;

            if(*p == (char_t)starting[0] && begin) {
              point_t i = current-curbp->b_page;
              if(chars[i][0] == ev.ch) {
                match = TRUE;
                tp = ptr(curbp, current+1);
                printf_tb(curwp->w_left+x, curwp->w_top+y, TB_RED, TB_CYAN, "%c", chars[i][1]);
                printf_tb(curwp->w_left+x+1, curwp->w_top+y, TB_DEFAULT, TB_DEFAULT, "%c", *tp == '\n' ? ' ' : *tp);
              } else {
                printf_tb(curwp->w_left+x, curwp->w_top+y, TB_DEFAULT, TB_DEFAULT, "%c", *tp == '\n' ? ' ' : *tp);
                tp = ptr(curbp, current+1);
                printf_tb(curwp->w_left+x+1, curwp->w_top+y, TB_DEFAULT, TB_DEFAULT, "%c", *tp == '\n' ? ' ' : *tp);
              }
              begin = FALSE;
            }

            if(!is_white && !is_symb)
              begin = FALSE;

            x++;
            if(*p == '\t')
              x += (TAB_SIZE - 2);
            if(*p < 31)
              x++;
            if(*p == '\n' || x >= curwp->w_cols) {
              x = 0;
              y++;
            }
          }
        }
        j++;
        if(!match) {
          clrtoeol(0, MSGLINE);
          return;
        }
      }
    }
  } else {
    num[0] = 'f';
    num[1] = 'f';
  }
  for(point_t cur = 0; cur < diff; cur++) {
    if(chars[cur][0] == num[0] && chars[cur][1] == num[1]) {
      point = cur + curbp->b_page;
      break;
    }
  }
  if(point == -1) {
    msg("Out of bounds.");
  } else {
    shift_pmark(TRUE, curbp->b_point);
    curbp->b_point = point;
    int cols = 0;
    /* Calculate the pcol value */
    lnbegin();      // reset the line so we get the right number for `cols`
    while(curbp->b_point != point) {
      ++curbp->b_point;
      cols++;
    }
    /* loop until we get to the correct column */
    while(cols > curwp->w_cols) {
      cols -= curwp->w_cols;
    }
    curbp->b_pcol = cols + curwp->w_left;      // set it for column-memory
    clrtoeol(0, MSGLINE);
  }

  /* Clear out the chars array */
  for(int i = 0; i < diff; i++) {
    chars[i][0] = 0;
    chars[i][1] = 0;
  }
  shift_pmark(TRUE, curbp->b_point);
  /* TODO: figure out why this has to be here
     Without this printf, the chars array doesn't appear to get
     cleared entirely and you end up jumping to the wrong points.
  */
  printf("%s", chars[0]);
}

void get_current_path(char *cur_path)
{
  int cutoff = 0;
  for(int i = strlen(curbp->b_fname) - 1; i > -1; i--) {
    if(curbp->b_fname[i] == '/') {
      cutoff = i;
      break;
    }
  }
  for(int i = 0; i <= cutoff; i++)
    cur_path[i] = curbp->b_fname[i];
  cur_path[cutoff+1] = '\0';
}

void insertfile()
{
  char cur_path[PATH_MAX] = "\0";
  if(curbp->b_path) {
    get_current_path(cur_path);
    strcpy(temp, cur_path);
  }
  else
    strcpy(temp, editor_dir);

  if (getfilename("Insert file: ", temp, PATH_MAX))
    (void)insert_file(temp, TRUE);
}

void readfile()
{
  buffer_t *bp;
  char cur_path[PATH_MAX];

  if(curbp->b_path) {
    get_current_path(cur_path);
    strcpy(temp, cur_path);
  }
  else
    strcpy(temp, editor_dir);

  int result = getfilename("Find file: ", (char*)temp, PATH_MAX);

  if (result) {
    bp = find_buffer(temp, TRUE, FALSE);
    disassociate_b(curwp);
    curbp = bp;
    associate_b2w(curbp, curwp);
    if (!growgap(curbp, CHUNK))
      fatal("%s: Failed to allocate required memory.\n");
    movegap(curbp, 0);
    /* load the file if not already loaded */
    if (bp != NULL) {
      if (!load_file(temp)) {
        msg("New file %s", temp);
      }
    }
  }
}

void savebuffer()
{
  const char *message = "No newline at the end of file, add one (Y/n) ?";
  int size = strlen(message);
  if(curbp->b_flags & B_MODIFIED) {
    /* move the gap to point 0 so that the ebuf is updated. */
    (void) movegap(curbp, 0);
    if(*(curbp->b_ebuf - 1) != '\n') {
      print_to_msgline(message);
      clrtoeol(size, MSGLINE);
      if (yesno(TRUE)) {
        clrtoeol(0, MSGLINE);
        *curbp->b_ebuf++ = '\n';
      }
    }
    if (curbp->b_fname[0] != '\0') {
      save(curbp->b_fname);
      return;
    } else {
      writefile();
    }
  } else {
    msg("(No changes need to be saved.)");
  }
}

void writefile()
{
  const char *message = "Write file: ";
  int size = strlen(message);
  strncpy(temp, curbp->b_fname, PATH_MAX);
  if (getinput((char *)message, temp, PATH_MAX, F_NONE, FALSE))
    if (save(temp) == TRUE)
      strncpy(curbp->b_fname, temp, PATH_MAX);
  clrtoeol(size, MSGLINE);
}

void killbuffer()
{
  buffer_t *kill_bp = curbp;
  buffer_t *bp;
  int bcount = count_buffers();
  const char *message = "Discard changes (y/N) ?";
  int size = strlen(message);

  /* do nothing if only buffer left is the scratch buffer */
  if (bcount == 1 && 0 == strcmp(get_buffer_name(curbp), "*scratch*"))
    return;

  if (curbp->b_flags & B_MODIFIED) {
    print_to_msgline(message);
    clrtoeol(size, MSGLINE);
    if (!yesno(FALSE))
      return;
  }

  if (bcount == 1) {
    /* create a scratch buffer */
    bp = find_buffer("*scratch*", TRUE, FALSE);
    strncpy(bp->b_bname, "*scratch*", STRBUF_S);
    bp->b_path = FALSE;
  }

  next_buffer();
  assert(kill_bp != curbp);
  delete_buffer(kill_bp);
  for(window_t *wp = wheadp; wp != NULL; wp = wp->w_next) {
    if(kill_bp == wp->w_bufp) {
      wp->w_bufp = curbp;
    }
  }
  clrtoeol(0, MSGLINE);
}

void iblock()
{
  block();
  msg("Mark set");
}

void unmark()
{
  shift_pmark(TRUE, NOMARK);
  curbp->b_mark = NOMARK;
  universal_argument = 0;
  msg("Mark removed");
}

void toggle_overwrite_mode() {
  if (curbp->b_flags & B_OVERWRITE)
    curbp->b_flags &= ~B_OVERWRITE;
  else
    curbp->b_flags |= B_OVERWRITE;
}

void killtoeol()
{
  if (curbp->b_point == pos(curbp, curbp->b_ebuf))
    return; /* do nothing if at end of file */
  if (*(ptr(curbp, curbp->b_point)) == 0xa) {
    delete(); /* delete CR if at start of empty line */
  } else {
    curbp->b_mark = curbp->b_point;
    lnend();
    if (curbp->b_mark != curbp->b_point) {
      currentcommand = KBD_CUT;
      copy_cut(TRUE, TRUE, FALSE);
    }
  }
}

/* Since version 1.9, you can use back-word-delete and
   fwd-word-delete to delete (and cut) a string of text so long that
   you don't interrupt the use of that direction of deletion. In
   other words, if you had the follow text and performed two `M-d`,
   you'd get both words in the scrap:
     hello there
     ^
     |
     point is here

   This means I have to concat, in the correct order, what you've cut
   in the scrap. To do this, I need the original scrap size and value
   then do some stuff to merge them together.
*/
void copy_cut(int cut, int displaymsg, int internal)
{
  char_t *p, *os, *ns = NULL, *kr;
  int shouldconcat = FALSE, onscrap = scrap.len, hasscrap = TRUE;
  /* if no mark or point == marker, nothing doing */
  if (curbp->b_mark == NOMARK || curbp->b_point == curbp->b_mark)
    return;
  if(cut && !internal) {
    /* We only concat if it's a word delete */
    shouldconcat = lastcommand == KBD_DELETE_WORD &&
      currentcommand == KBD_DELETE_WORD ?
      curbp->b_point - curbp->b_mark : FALSE;
    undoset(CUT, shouldconcat);
  }
  if(!shouldconcat || scrap.data == NULL) {
    hasscrap = FALSE;
    onscrap = 0;
    for(int i = KILLRING_SIZE-1; i > 0; i--) {
      if(kill_ring[i].data != NULL) {
        free(kill_ring[i].data);
        kill_ring[i].data = NULL;
      }
      if(kill_ring[i-1].data != NULL) {
        char_t *kri;
        kri = (char_t *)strndup((const char *)kill_ring[i-1].data, kill_ring[i-1].len);
        kri[kill_ring[i-1].len] = '\0';
        if (kri == NULL) {
         msg("No more memory available.");
         return;
        } else {
          kill_ring[i].data = kri;
          kill_ring[i].len = kill_ring[i-1].len;
        }
      }
    }
    if(kill_ring[0].data != NULL) {
      free(kill_ring[0].data);
      kill_ring[0].data = NULL;
    }
    kr = (char_t *)strndup((const char*)scrap.data, scrap.len);
    kr[scrap.len] = '\0';
    if (kr == NULL) {
      msg("No more memory available.");
     return;
    } else {
      kill_ring[0].data = kr;
      kill_ring[0].len = scrap.len;
    }
  }
  if (curbp->b_point < curbp->b_mark) {
    /* point above marker: move gap under point, region = marker - point */
    (void) movegap(curbp, curbp->b_point);
    p = ptr(curbp, curbp->b_point);
    scrap.len = curbp->b_mark - curbp->b_point;
    if(cut && currentcommand == KBD_DELETE_WORD)
      for(point_t pt = curbp->b_mark-1; pt > curbp->b_point; pt--) {
        if(*ptr(curbp, pt) == '\n')
          curbp->b_line--;
      }
  } else {
    /* if point below marker: move gap under marker, region = point - marker */
    (void) movegap(curbp, curbp->b_mark);
    p = ptr(curbp, curbp->b_mark);
    scrap.len = curbp->b_point - curbp->b_mark;
    if (cut && currentcommand != KBD_DELETE_WORD)
      for(point_t pt = curbp->b_mark; pt < curbp->b_point; pt++) {
        if(*ptr(curbp, pt) == '\n')
          curbp->b_line--;
      }
  }
  if (shouldconcat) {
    ns = (char_t *) strndup((const char *)p, scrap.len);
    ns[scrap.len] = '\0';
    if(shouldconcat < 0 && hasscrap) { /* deleting with M-<backsp> */
      asprintf((char **)&os, "%s%s", ns, scrap.data);
      os[onscrap + scrap.len] = '\0';
      if(scrap.data != NULL)
        free(scrap.data);
      scrap.data = os;
      free(ns);
      ns = NULL;
    } else if(shouldconcat > 0 && hasscrap) { /* deleting with M-d */
      asprintf((char **)&os, "%s%s", scrap.data, ns);
      os[onscrap + scrap.len] = '\0';
      if(scrap.data != NULL)
        free(scrap.data);
      scrap.data = os;
      free(ns);
      ns = NULL;
    } else /* first time deleting */
      scrap.data =  ns;
    scrap.len = onscrap + scrap.len;
  } else {
    if (scrap.data != NULL) {
      free(scrap.data);
      scrap.data = NULL;
    }
    ns = (char_t *) strndup((const char *)p, scrap.len);
    ns[scrap.len] = '\0';
    if (ns == NULL) {
      msg("No more memory available.");
      return;
    } else {
      scrap.data = ns;
    }
  }
  if (cut) {
    /* note that we only need to expand the gap by the amount being
       concated
    */
   curbp->b_egap += scrap.len - onscrap; /* if cut expand gap down */
   curbp->b_point = pos(curbp, curbp->b_egap); /* set point to after region */
   curbp->b_flags |= B_MODIFIED;
   if(displaymsg)
      msg("%ld bytes cut.", scrap.len);
//       currentcommand = KBD_CUT;
  } else {
    if(displaymsg)
      msg("%ld bytes copied.", scrap.len);
  }
  curbp->b_mark = NOMARK;  /* unmark */
}

void paste_internal(int internal)
{
  int new_rows = 0;
  int col = curwp->w_col - curwp->w_left + 1;
  point_t opoint = curbp->b_point;
  if(curbp->b_flags & B_OVERWRITE)
    return;
  if (scrap.len <= 0) {
    msg("Scrap is empty.  Nothing to yank.");
  } else if (scrap.len < curbp->b_egap - curbp->b_gap || growgap(curbp, scrap.len)) {
    if(!internal)
      undoset(YANK, FALSE);
    curbp->b_point = movegap(curbp, curbp->b_point);
    memcpy(curbp->b_gap, scrap.data, scrap.len * sizeof (char_t));
    curbp->b_gap += scrap.len;
    curbp->b_point = pos(curbp, curbp->b_egap);
    curbp->b_flags |= B_MODIFIED;
    /* TODO: this assumes 1 char = 1 point (not always true) */
    col += curbp->b_point - opoint;
    for(int i = 0, cc = col; scrap.data[i] != '\0'; i++) {
      cc++;
      if(scrap.data[i] == '\n' || cc >= curwp->w_cols) {
        new_rows++;
        cc = 0;
      }
      curbp->b_pcol = cc + curwp->w_left;
    }
    if ((curbp->b_row - curwp->w_top) + new_rows >= curwp->w_rows)
      curbp->b_reframe = 1;
  }
}

void paste()
{
  char_t *oscrap, *new_scrap = NULL;
  int onscrap;

  currentcommand = KBD_YANK;
  if(universal_argument > 0 && universal_argument-1 < KILLRING_SIZE) {
    oscrap = (char_t *)strndup((char *)scrap.data, scrap.len);
    onscrap = scrap.len;
    free(scrap.data);
    scrap.len = kill_ring[universal_argument-1].len;
    new_scrap = (char_t *) strndup(
      (const char *)kill_ring[universal_argument-1].data,
      scrap.len
    );
    scrap.data = new_scrap;

    paste_internal(FALSE);

    free(scrap.data);
    scrap.len = onscrap;
    scrap.data = oscrap;
    return;
  }
  paste_internal(FALSE);
}

void clipboard()
{
  int new_rows = 0, alen = 0;
  int col = curwp->w_col - curwp->w_left + 1;
  point_t opoint = curbp->b_point;
  if(curbp->b_flags & B_OVERWRITE)
    return;
  /* since we realloc gtemp by sizes of TEMPBUF, we will have a lot
     of null terms at the end. We only want to grow the gap by the
     correct about of non-null chars.
  */
  for(alen = 0; gtemp[alen] != '\0' && alen < ngtemp; alen++)
      ;;
  if (ngtemp <= 0 || alen <= 0) {
    msg("Temp buffer is empty.  Nothing to paste.");
    return;
  }
  if (ngtemp < curbp->b_egap - curbp->b_gap || growgap(curbp, alen)) {
    undoset(CLIPBOARD, FALSE);
    curbp->b_point = movegap(curbp, curbp->b_point);
    memccpy(curbp->b_gap, gtemp, '\0', alen);
    curbp->b_gap += alen;
    curbp->b_point = pos(curbp, curbp->b_egap);
    curbp->b_flags |= B_MODIFIED;
    /* TODO: this assumes 1 char = 1 point (not always true) */
    col += curbp->b_point - opoint;
    for(int i = 0, cc = col; gtemp[i] != '\0'; i++) {
      cc++;
      if(gtemp[i] == '\n' || cc >= curwp->w_cols) {
        new_rows++;
        cc = 0;
      }
      curbp->b_pcol = cc + curwp->w_left;
    }
    if ((curbp->b_row - curwp->w_top) + new_rows >= curwp->w_rows)
      curbp->b_reframe = 1;
  } else {
    return;
  }
  if(gtemp != NULL) {
    free(gtemp);
    gtemp = NULL;
    ngtemp = 0;
  }
}

void showpos()
{
  int current, lastln;
  point_t end_p = pos(curbp, curbp->b_ebuf);

  get_line_stats(&current, &lastln, curbp);

  if (curbp->b_point == end_p) {
    msg("[EOB] Line = %d/%d  Point = %d/%d", current, lastln,
      curbp->b_point, ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap)));
  } else {
    char c = unctrl(*(ptr(curbp, curbp->b_point)));
    msg("Char = %c 0x%x  Line = %d/%d  Point = %d/%d", c, *(ptr(curbp, curbp->b_point)),
      current, lastln,
      curbp->b_point, ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap)));
  }
}

void insertnewlinebelow()
{
  input[0] = '\n';
  input[1] = '\0';
  undoset(INSERT, FALSE);
  insert();
  curbp->b_point--;
  currentcommand = KBD_DEFAULT;
}

void insertnewline()
{
  point_t point;
  char_t *p, *space = NULL;
  int spaces = 0, i;

  point = segstart(curbp, curwp, lnstart(curbp, curbp->b_point), curbp->b_point);
  while(point < pos(curbp, curbp->b_ebuf) &&
        isspace(*(p = ptr(curbp, point))) &&
        *p != '\n' &&
        curwp->w_col != 0) {
    if(spaces == 0) {
      space = p;
    }
    if(*p != '\n') {
      spaces++;
      point++;
    }
  }
  input[0] = '\n';
  for(i = 0; i < spaces; i++) {
    input[i+1] = *space;
  }
  i++;
  input[i] = '\0';
  insert_str();
  curbp->b_pcol = spaces + curwp->w_left;
  /* this stops the annoying reframing */
  curbp->b_epage += spaces ? spaces : 1;
  currentcommand = KBD_INSERT;

  if((curwp->w_row - curwp->w_top) == curwp->w_rows-1) {
    curbp->b_reframe = TRUE;
  }
}

void inserttab()
{
  input[0] = '\t';
  input[1] = '\0';
  undoset(INSERT, FALSE);
  insert();
}

void inserttabasspace()
{
  memset(input, ' ', TAB_SPACE_SIZE+1);
  input[TAB_SPACE_SIZE] = '\0';
  curbp->b_epage += TAB_SPACE_SIZE;
  insert_str();
}

void suspend()
{
  tb_shutdown();
  raise(SIGTSTP);
}

void transpose()
{
  char_t *cur = ptr(curbp, curbp->b_point);
  char_t *prev = ptr(curbp, curbp->b_point-1);
  char_t replace[3];
  if(cur == curbp->b_ebuf) {
    return;
  }
  point_t mark = curbp->b_mark;
  replace[0] = *cur;
  replace[1] = *prev;
  replace[2] = '\0';
  curbp->b_point--;
  curbp->b_mark = curbp->b_point + 2;
  undoset(REPLACE, 2);
  curbp->b_mark = mark;
  curbp->b_point++;
  memcpy(ptr(curbp, curbp->b_point-1), replace, 2 * sizeof (char_t));
  curbp->b_flags |= B_MODIFIED;
}

void transposeword()
{
  point_t op;
  wright();
  wleftdelete();
  op = curbp-> b_point;
  wleft();
  paste_internal(FALSE);
  op += scrap.len;
  wrightdelete();
  op -= scrap.len;
  curbp->b_point = op;
  paste_internal(FALSE);
}

void lowercaseword()
{
  char_t *p, *word;
  point_t sword, eword;
  int olast = lastcommand;
  while ((isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)) && p < curbp->b_ebuf)
    ++curbp->b_point;
  sword = curbp->b_point;
  wright();
  eword = curbp->b_point;
  word = (char_t *) malloc(sizeof(char_t)*(eword - sword));
  curbp->b_point = sword;
  lastcommand = KBD_DELETE_CHAR;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    word[k] = *ptr(curbp, curbp->b_point);
    delete();
  }
  lastcommand = olast;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    input[0] = tolower(word[k]);
    input[1] = '\0';
    undoset(INSERT, i != 0);
    insert();
  }
  free(word);
}

void capitalizeword()
{
  char_t *p;
  while (isspace(*(p = ptr(curbp, curbp->b_point))) && p < curbp->b_ebuf)
    ++curbp->b_point;
  p = ptr(curbp, curbp->b_point);
  input[0] = toupper(*p);
  input[1] = '\0';
  delete();
  undoset(INSERT, FALSE);
  insert();
  if(isspace(*(p = ptr(curbp, curbp->b_point+1))) || is_symbol(*p))
    curbp->b_point++;
  else
    wright();
}

void uppercaseword()
{
  char_t *p, *word;
  point_t sword, eword;
  int olast = lastcommand;
  while ((isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)) && p < curbp->b_ebuf)
    ++curbp->b_point;
  sword = curbp->b_point;
  wright();
  eword = curbp->b_point;
  word = (char_t *) malloc(sizeof(char_t)*(eword - sword));
  curbp->b_point = sword;
  lastcommand = KBD_DELETE_CHAR;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    word[k] = *ptr(curbp, curbp->b_point);
    delete();
  }
  lastcommand = olast;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    input[0] = toupper(word[k]);
    input[1] = '\0';
    undoset(INSERT, i != 0);
    insert();
  }
  free(word);
}

/* type = 0, zap
   type = 1, jump
*/
/* TODO: Throw error when putting non-char in.
*/
int gotochar(int type, int include_char)
{
  char_t *p;
  point_t opoint = curbp->b_point, eol;
  int c, col = 0;
  struct tb_event ev;
  char *promptBeg = type == 0 ? "Zap to Char" : "Jump to Char";
  char *prompt;

  if(lastchar == 0)
    asprintf(&prompt, "%s: ", promptBeg);
  else
    asprintf(&prompt, "%s (default %c): ", promptBeg, lastchar);

  if(character[0] == '\0') {
    display_prompt_and_response(prompt, character);
    tb_present();
    if(execute_kbd_macro) {
      use_kbd_macro(&ev);
    } else if(tb_poll_event(&ev) != TB_OK)
      return 0;

    if(!ev.mod)
      c = ev.ch;
    else
      c = ev.key;

    if(record_input) {
      record_buffer[record_buffer_index] = ev;
      record_buffer_index++;
    }

    /* Ignore all control keys other than C-g, ESC, and return */
    if (c < 32 &&
        c != TB_KEY_CTRL_G &&
        c != TB_KEY_ESC &&
        c != TB_KEY_CTRL_I &&
        c != TB_KEY_TAB &&
        c != TB_KEY_ENTER)
      return 0;
    if(c == TB_KEY_CTRL_G || c == TB_KEY_ESC)
      return 0;
    else if (c == TB_KEY_ENTER)
      character[0] = lastchar;
    else
      character[0] = c;
    display_prompt_and_response(prompt, character);
    tb_present();
  }

  if(type == 0) {
    block();
  }
  if(*ptr(curbp, curbp->b_point) == character[0] || curbp->b_point == 0) {
    if(negated)
      left();
    else
      right();
  }
  while (*(p = ptr(curbp, curbp->b_point + (include_char ? 0 : (negated ? -1 : 1)))) != character[0]) {
    if(negated) {
      if(curbp->b_point == 0)
        break;
      left();
    } else {
      if(p == curbp->b_ebuf)
        break;
      right();
    }
  }

  if(type == 0 && !negated)
    right();

  if(type == 0) {
    currentcommand = KBD_CUT;
    copy_cut(TRUE, FALSE, FALSE);
  }

  tb_set_cursor(0, MSGLINE);
  clrtoeol(0, MSGLINE);
  eol = lnstart(curbp, curbp->b_point);
  for(point_t poi = curbp->b_point; poi > eol; poi -= utf8_size(*ptr(curbp,poi)))
    col++;
  curbp->b_pcol = col + curwp->w_left;
  if(p >= ptr(curbp, curbp->b_epage)) {
    curbp->b_reframe = TRUE;
  }
  if((!negated && p >= curbp->b_ebuf) || (negated && curbp->b_point <= 0)) {
    msg("No match found.");
    curbp->b_point = opoint;
    negated = FALSE;
    lastchar = character[0];
    return 0;
  }
  negated = FALSE;
  lastchar = character[0];
  return 1;
}

/* Delete whitespace between non-whitespace */
void delete_between()
{
  char_t *p, other;
  struct tb_event ev;
  char *prompt;
  int c, is_start = FALSE, depth = 0, pass = FALSE;
  /* Delete everything between brackets. */
  if(universal_argument > 0) {
    if(character[0] == '\0') {
      if(lastsymb == 0)
        asprintf(&prompt, "Bracket to Zap Between: ");
      else
        asprintf(&prompt, "Bracket to Zap Between (default %c): ", lastsymb);
      display_prompt_and_response(prompt, character);
      tb_present();

      if(execute_kbd_macro) {
        use_kbd_macro(&ev);
      } else if(tb_poll_event(&ev) != TB_OK)
        return;

      if(!ev.mod)
        c = ev.ch;
      else
        c = ev.key;

      if(record_input) {
        record_buffer[record_buffer_index] = ev;
        record_buffer_index++;
      }

      /* Ignore all control keys other than C-g, ESC, and return */
      if (c < 32 && c != TB_KEY_CTRL_G && c != TB_KEY_ESC && c != TB_KEY_ENTER)
      return;
      if(c == TB_KEY_CTRL_G || c == TB_KEY_ESC)
        return;
      else if(c == TB_KEY_ENTER)
        character[0] = lastsymb;
      else
        character[0] = c;
      display_prompt_and_response(prompt, character);
      tb_present();
    }
    if(!(other = is_bracket(character[0], TRUE, &is_start))) {
      return;
    }
    for(;universal_argument > 0; universal_argument--)
      pass = gotochar(1, TRUE);
    if(!pass) {
      return;
    }
    adjust_bline();
    universal_argument = 0;
    lastcommand = KBD_DELETE_CHAR;
    if(is_start) {
      curbp->b_point++;
      while ((*(p = ptr(curbp, curbp->b_point)) != other &&
              curbp->b_buf < p) ||
             depth > 0) {
        if(*p == character[0]) {
          depth++;
        } else if(*p == other) {
          depth--;
        }
        delete();
      }
    } else {
      while ((*(p = ptr(curbp, curbp->b_point - 1)) != other &&
              curbp->b_buf < p) ||
             depth > 0) {
        if(*p == character[0]) {
          depth++;
        } else if(*p == other) {
          depth--;
        }
        backsp();
      }
    }
    lastsymb = character[0];
    character[0] = '\0';
    return;
  }

  /* If in a word delete the word both directions.
     This is the same as doing a `esc f` then `esc backsp`.
     This does not delete the symbols, just the words.
  */
  if(!isspace(*ptr(curbp, curbp->b_point - 1)) &&
     !isspace(*ptr(curbp, curbp->b_point)) &&
     !isspace(*ptr(curbp, curbp->b_point + 1))) {

    wright();
    wleftdelete();

    return;
  }

  lastcommand = KBD_DELETE_CHAR;
  /* Otherwise just delete whitespace */
  while (isspace(*(p = ptr(curbp, curbp->b_point - 1))) && curbp->b_buf < p && *p != '\n')
    backsp();
  if(isspace(*(p = ptr(curbp, curbp->b_point - 1))) && *p != '\n')
    backsp();
  while (isspace(*(p = ptr(curbp, curbp->b_point))) && curbp->b_buf <= p && *p != '\n')
    delete();
}

void zaptochar()
{
  gotochar(0, universal_argument == 0);
  universal_argument = 0;
}

void negated_zaptochar()
{
  negated = TRUE;
  gotochar(0, universal_argument == 0);
  universal_argument = 0;
}

void jumptochar()
{
  shift_pmark(TRUE, curbp->b_point);
  gotochar(1, TRUE);
  shift_pmark(TRUE, curbp->b_point);
}

void negated_jumptochar()
{
  shift_pmark(TRUE, curbp->b_point);
  negated = TRUE;
  gotochar(1, TRUE);
  shift_pmark(TRUE, curbp->b_point);
}

void poptomark()
{
  point_t p;
  if(curbp->b_mark != NOMARK)
    curbp->b_point = curbp->b_mark;
  else if(curbp->b_pmark[0] != NOMARK) {
    p = shift_pmark(FALSE, NOMARK);
    if(p == curbp->b_point) {
      p = shift_pmark(FALSE, NOMARK);
    }
    curbp->b_point = p;
  } else {
    msg("No valid mark to pop to.");
    return;
  }
  if(curbp->b_point < curbp->b_page || curbp->b_point > curbp->b_epage) {
    curbp->b_reframe = TRUE;
    curwp->w_recenter = TRUE;
  }
}

void universal_argument_load()
{
  universal_argument++;
  msg("C-u %d", universal_argument);
}

void numeric_argument_load()
{
  numeric_argument = (numeric_argument * 10) + atoi((const char *)&input_char);
  msg("C-u %d", numeric_argument);
}

void back_to_indentation()
{
  char_t *p;
  while (isspace(*(p = ptr(curbp, curbp->b_point))) && p < curbp->b_ebuf)
    ++curbp->b_point;
}

void negate()
{
  negated = !negated;
  msg("C-u -");
}

void forward_bracket()
{
  point_t p, eol;
  int col = 0;
  if((p = find_matching_bracket(curbp, curwp, 1, FALSE)) >= 0)
    curbp->b_point = curbp->b_mark == NOMARK ? p : p + 1;
  /* Make sure the column memory updates to the new column */
  eol = lnstart(curbp, curbp->b_point);
  for(p = curbp->b_point; p > eol; p -= utf8_size(*ptr(curbp,p)))
    col++;
  curbp->b_pcol = col + curwp->w_left;
}

void backward_bracket()
{
  point_t p, eol;
  int col = 0;
  if((p = find_matching_bracket(curbp, curwp, -1, FALSE)) >= 0) {
    curbp->b_point = p;
    if(curbp->b_mark != NOMARK)
      curbp->b_mark++;
  }
  /* Make sure the column memory updates to the new column */
  eol = lnstart(curbp, curbp->b_point);
  for(p = curbp->b_point; p > eol; p -= utf8_size(*ptr(curbp,p)))
    col++;
  curbp->b_pcol = col + curwp->w_left;
}

void start_kbd_macro()
{
  record_input = TRUE;
  for(int i = 0; i < record_buffer_index; i++) {
    memset(&record_buffer[i], 0, sizeof(record_buffer[i]));
  }
  record_buffer_index = 0;
  msg("Started keyboard macro...");
}

void end_kbd_macro()
{
  record_input = FALSE;
  msg("Ended keyboard macro.");
}

void run_kbd_macro()
{
  if(numeric_argument > 0)
    numeric_argument--;
  /* If you start_kbd_macro and immediately close it, you haven't
     really recorded anything. This shows up as the second value
     being C-x and then 0 in the 3rd.
  */
  if(record_buffer_index == 0 ||
    (record_buffer[1].key == TB_KEY_CTRL_X && record_buffer[2].key == 0)) {
    msg("No recorded keyboard macro.");
    return;
  }
  if(record_input) {
    msg("Currently recording keyboard macro.");
    return;
  }
  execute_kbd_macro = TRUE;
}

void open_file_from_shell()
{
  io = 1;
  get_popen_data();
}

void insert_from_shell()
{
  io = 0;
  get_popen_data();
}

void insert_control_char()
{
  struct tb_event ev;
  char *prompt = "Insert Control Char: ";

  display_prompt_and_response(prompt, character);
  tb_present();
  if(execute_kbd_macro) {
    use_kbd_macro(&ev);
  } else if(tb_poll_event(&ev) != TB_OK)
    return;
  if(record_input) {
    record_buffer[record_buffer_index] = ev;
    record_buffer_index++;
  }
  tb_set_cursor(0, MSGLINE);
  clrtoeol(0, MSGLINE);
  if(ev.key > 0x1a) {
    return;
  }
  input[0] = (char)ev.key;
  input[1] = '\0';
  undoset(INSERT, lastcommand == KBD_INSERT);
  insert();
  currentcommand = KBD_INSERT;
  ignorenotbound = TRUE;
}

void comment_at_eol()
{
  if(curbp->b_mode == NULL || curbp->b_mode->slc == NULL) {
    return;
  }

  lnend();
  inserttabasspace();
  for(int c = 0; curbp->b_mode->slc[c] != '\0'; c++)
    input[c] = curbp->b_mode->slc[c];
  input[strlen(curbp->b_mode->slc)] = ' ';
  input[strlen(curbp->b_mode->slc)+1] = '\0';
  insert_str();
}

int single_line_comment()
{
  point_t p = curbp->b_point, op = -1;
  int nslc = strlen(curbp->b_mode->slc);
  int match = FALSE, i = 0;
  char_t* c;

  if(op != -1)
    p = op;
  lnbegin();
  for(i = 0; curbp->b_mode->slc[i] != '\0'; i++) {
    if(*(c = ptr(curbp, curbp->b_point)) == curbp->b_mode->slc[i]) {
      match = TRUE;
    } else {
      match = FALSE;
    }
  }
  if(match) {
    for(; i > 0; i--)
      delete();
    delete();    // don't forget the extra space
    return -1 * (nslc + 1);
  }
  for(int c = 0; curbp->b_mode->slc[c] != '\0'; c++)
    input[c] = curbp->b_mode->slc[c];
  input[nslc] = ' ';
  input[nslc+1] = '\0';
  insert_str();
  curbp->b_point = p + nslc + 1;
  return nslc + 1;
}

void comment()
{
  point_t p = curbp->b_point, op = -1, mark = curbp->b_mark;
  char_t *c;
  int match = FALSE, i = 0, e = 0, j = 0;
  int oline = curbp->b_line;
  int newline = curbp->b_line, extra = 0;

  if(curbp->b_mode == NULL || curbp->b_mode->slc == NULL) {
    return;
  }

  /* multi-line */
  if(mark != NOMARK) {
    if(curbp->b_mode->mlc != NULL &&
       curbp->b_mode->emlc != NULL &&
       universal_argument == 0) {
      copy_cut(TRUE, FALSE, FALSE);
      for(j = 0; curbp->b_mode->mlc[j] != '\0'; j++)
        input[j] = curbp->b_mode->mlc[j];
      input[j+1] = '\0';
      insert_str();
      extra = j;
      for(j = 0; curbp->b_mode->emlc[j] != '\0'; j++)
        input[j] = curbp->b_mode->emlc[j];
      input[j] = '\0';
      insert_str();
      extra += j;
      for(; j > 0; j--)
        left();
      curbp->b_opoint = curbp->b_point;
      paste_internal(FALSE);
      curbp->b_point = p + extra;
      return;
    } else { // end of multi-line
      /* comment out multiple lines with a single line comment */
      if(mark > curbp->b_point) {
        while(curbp->b_point < mark) {
          int len = single_line_comment();
          extra += len;
          /* the mark point changes as you remove comment symbols */
          mark += len;
          down();
          lnbegin();
        }
        curbp->b_mark = NOMARK;
        return;
      } else {
        curbp->b_point = mark;
        lnbegin();
        mark = curbp->b_point;
        curbp->b_point = p;
        while(curbp->b_point >= mark && curbp->b_point > 0) {
          extra += single_line_comment();
          up();
          lnbegin();
        }
        if(mark == 0) {
          extra += single_line_comment();
        }
        curbp->b_mark = NOMARK;
        curbp->b_point = p + extra;
        return;
      }
    }
  }

  /* Check to see if you're in a multi-line comment.
     If you see the end of a multi-line comment, you know immediately
     that you aren't in one.
  */
  if(curbp->b_mode->mlc != NULL &&
     curbp->b_mode->emlc != NULL) {
    op = p;
    for(e = strlen(curbp->b_mode->emlc) - 1, i = strlen(curbp->b_mode->mlc) - 1;
        p > 0 && i >= 0 && e >= 0;
        p--) {
      int smatch = *(c = ptr(curbp, p)) == curbp->b_mode->mlc[i];
      int ematch = *c == curbp->b_mode->emlc[e];
      if(*c == '\n')
        newline--;
      if(smatch) {
        if(i == 0)
          break;
        i--;
      }
      if(ematch) {
        if(e == 0)
          break;
        e--;
      }
      if(!smatch) {
        i = strlen(curbp->b_mode->mlc) - 1;
      }
      if(!ematch) {
        e = strlen(curbp->b_mode->emlc) - 1;
      }
    }
  }

  /* If you're in a multi-line comment, remove it. */
  if(i <= 0 && e > 0) {
    curbp->b_point = p;
    curbp->b_line = newline;
    for(i = strlen(curbp->b_mode->mlc); i > 0; i--)
      delete();
    match = FALSE;
    for(i = 0; p < pos(curbp, curbp->b_ebuf) && i < strlen(curbp->b_mode->emlc); p++) {
      if(*(c = ptr(curbp, p)) == curbp->b_mode->emlc[i]) {
        match = TRUE;
        i++;
      } else {
        match = FALSE;
      }
      if(*c == '\n')
        curbp->b_line++;
    }
    if(match) {
      p -= i;
      curbp->b_point = p;
      for(; i > 0; i--)
        delete();
    }
    curbp->b_point = op - strlen(curbp->b_mode->mlc);
    curbp->b_line = oline;
    return;
  }

  /* single line */
  if(universal_argument > 0 &&
     curbp->b_mode->mlc != NULL &&
     curbp->b_mode->emlc != NULL) {
    j = 0;
    for(; curbp->b_mode->mlc[j] != '\0'; j++)
      input[j] = curbp->b_mode->mlc[j];
    input[j+1] = '\0';
    insert_str();
    op = curbp->b_point;
    j = 0;
    for(; curbp->b_mode->emlc[j] != '\0'; j++)
      input[j] = curbp->b_mode->emlc[j];
    input[j+1] = '\0';
    insert_str();
    curbp->b_point = op;
    input[0] = ' ';
    input[1] = '\0';
    insert();
    insert();
    left();
  } else
     single_line_comment();
}

void dynamically_expand()
{
  point_t endpoint = pos(curbp, curbp->b_ebuf);
  int i = 0, j = 0;
  char_t *p, result[TEMPBUF];
  dynars_t *dyrs, *dr;

  if ((dyrs = (dynars_t *) malloc (sizeof(dynars_t))) == NULL) {
    msg("Failed to allocate required memory");
    return;
  }
  p = ptr(curbp, curbp->b_point);

  if(isalpha(*p) || isdigit(*p))
    return;

  if(dynaex.query == NULL) {
    dynaex.end = curbp->b_point;
    dynaex.start = curbp->b_point-1;
    while(isalpha(*(p = ptr(curbp, --dynaex.start))) || isdigit(*p) || *p == '_')
      ;;
    dynaex.sp = dynaex.start;
    dynaex.start++;
    dynaex.nquery = dynaex.end - dynaex.start;
    dynaex.nresult = dynaex.nquery;
    dynaex.query = (char_t *) calloc(dynaex.nquery+1, sizeof(char_t));
    p = ptr(curbp, dynaex.start);
    memccpy(dynaex.query, p, '\0', dynaex.nquery);
  }
  if(dynaex.results == NULL) {
    dynaex.results = dyrs;
    dynaex.results->result = NULL;
    dynaex.results->d_next = NULL;
  }

/* `i` and `j` are indices, hence the - 1.
   We start by working backwards and filling `result` once we start having
   a match. If anything doesn't match scrap the entire thing and keep going.
   Then, once a total match is made (when i == -1), we jump ahead of the
   match and collect the rest of the word. Here is an example, we are
   doing an expansion on "in":
   INclude
   We work backwards until we get to N and then I, which get loaded into
   `result`. Then we jump to the 'c' and collect "clude". This gives us
   the GNU Emacs style of dynamic-expand.

   One important note about single character expansion: We subtract the
   dynaex match point by one right before we use the restart label (goto
   restart), this prevents us from getting stuck at the previous match
   point.
*/
restart:
  i = dynaex.nquery-1;
  j = i;
  int dir = -1;
  point_t match = 0;
  while (!(dynaex.sp >= dynaex.start && dynaex.sp <= dynaex.end)) {
    if(dynaex.sp == -1) {
      dynaex.sp = endpoint;
    }
    p = ptr(curbp, dynaex.sp);
    if(i == -1) {
      if(!isalpha(*p) && !isdigit(*p) && *p != '_') {
        dynaex.sp =  match;
        break;
      }
      result[j] = *p;
      result[++j] = '\0';
    } else if(*p == dynaex.query[i]) {
      result[j] = dynaex.query[i];
      i--;
      j--;
      if(i == -1) {
        if(dynaex.sp == 0 || (!isalpha(*ptr(curbp, dynaex.sp-1)) &&
                              !isdigit(*ptr(curbp, dynaex.sp-1)))) {
          j = dynaex.nquery;
          dir = 1;
          match = dynaex.sp;
          dynaex.sp += dynaex.nquery - 1;
        } else {
          i = dynaex.nquery-1;
          j = i;
          dir = -1;
        }
      }
    } else if(i >= 0) {
      i = dynaex.nquery-1;
      j = i;
      dir = -1;
    }
    dynaex.sp += dir;
  }

  currentcommand = KBD_EXPAND;

  if(dynaex.sp >= dynaex.start && dynaex.sp <= dynaex.end) {
    msg("No dynamic expansion for \"%s\" found.", dynaex.query);
    /* Free the struct so we can loop again */
    dynaex.sp = dynaex.start - 1;
    dr = dynaex.results;
    while(dynaex.results != NULL) {
      dr = dynaex.results;
      dynaex.results = dynaex.results->d_next;
      if(dr->result != NULL) {
        free(dr->result);
        dr->result = NULL;
      }
      if(dr != NULL) {
        free(dr);
        dr = NULL;
      }
    }
    return;
  }

  /* Check if the match has been used before, if so, skip it. */
  for(dr = dynaex.results; dr != NULL && dr->result != NULL; dr = dr->d_next) {
    int k = 0;
    for(; dr->result[k] != '\0'; k++) {
      if(dr->result[k] != result[k])
        break;
    }
    if(dr->result[k] == '\0' && result[k] == '\0') {
      i = 0;
      j = 0;
      if(dynaex.nquery == 1)
        dynaex.sp--;
      goto restart;
    }
  }

  curbp->b_point = dynaex.start;
  point_t end = dynaex.start + dynaex.nresult;

  curbp->b_mark = end;
  undoset(REPLACE, j);
  curbp->b_mark = NOMARK;

  if (j > dynaex.nresult) {
    movegap(curbp, end);
    /*check enough space in gap left */
    if (j - dynaex.nresult < curbp->b_egap - curbp->b_gap)
      growgap(curbp, j - dynaex.nresult);
    /* shrink gap right by r - s */
    curbp->b_gap = curbp->b_gap + (j - dynaex.nresult);
  } else if (dynaex.nresult > j) {
    movegap(curbp, end);
    /* stretch gap left by s - r, no need to worry about space */
    curbp->b_gap = curbp->b_gap - (dynaex.nresult - j);
  } else {
    /* if rlen = slen, we just overwrite the chars, no need to move gap */
  }
   /* now just overwrite the chars at point in the buffer */
  memcpy(ptr(curbp, curbp->b_point), result, j * sizeof (char_t));
  curbp->b_flags |= B_MODIFIED;
  curbp->b_point = end + (j - dynaex.nresult);
  dynaex.nresult = j;

  /* Add the match to the matched list */
  if(dynaex.results->result == NULL) {
    dynaex.results->result = calloc(j+1, sizeof(char_t));
    memccpy(dynaex.results->result, result, '\0', j);
  } else {
    dyrs->result = calloc(j+1, sizeof(char_t));
    memccpy(dyrs->result, result, '\0', j);
    dyrs->d_next = NULL;
    for(dr = dynaex.results; dr->d_next != NULL; dr = dr->d_next)
      ;;
    dr->d_next = dyrs;
  }
}
